%% gen_proxy.erl
%%   Generating proxy modules for a class.
%%
%% Copyright (C) 2008, 2009 Gábor Fehér
%%
%% This file is part of ECT.
%% 
%% ECT is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% 
%% ECT is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%% GNU General Public License for more details.
%% 
%% You should have received a copy of the GNU General Public License
%% along with ECT.  If not, see <http://www.gnu.org/licenses/>.
%%
-module(gen_proxy).

-include("common_defs.hrl").

-export([create_proxy/1]).


%% Converts the vmt into an erlang exports list. Strips ImplementorClass from the tuples.
%% Parameters:
%%    The VMT of the current class.
%% Returns:
%%    List of {Name, Arity} pairs.

%% Function: create_exports(VMT)
%% Purpose: creates an exports list from a method table
%% Args:
%%   VMT: a list of the form [{Name, Arity, _, _}]. List of all the methods of the compiled class.
%% Returns:
%%   An abstract syntax tree representation of the exports list for the methods.
create_exports([]) ->
    [];
create_exports([{Name, Arity, _, _}|VMT]) ->
    [{Name, Arity}|create_exports(VMT)].


%% Function: implement_method_proxy_call(Name, Arity, Args, CompileInfo, Module, Line)
%% Purpose: creates the clause for a proxy functions, that calls the implementation of the method
%% Args:
%%   Name: name of method
%%   Arity: arity of the method
%%   Args: arguments of the method
%%   CompileInfo: general info for transforming the compiled class (including its definition)
%%   Module: Module where the method is implemented
%%   Line: line number to set in abstract syntax tree nodes
%% Returns:
%%   A one-element list, that contains the abstract syntax tree representation of the 
%%   implementation-calling clause of the proxy function.
%%   In case of using cctrans, a type-checking guard is added to the clause.
implement_method_proxy_call(Name, Arity, [ThisArg|_] = Args, #compile_info{class_definition = Definition, cctrans = CCTrans}, Module, Line) ->
    [{clause, Line, 
        ast:var_list(Arity, Line),
        case CCTrans of cctrans -> [[ast:class_typecheck_guard(Definition, ThisArg, Line)]]; cctrans0 -> [] end,
        [ast:rcall(Module, Name, Args, Line)]
    }].

%% Function: implement_method_proxy_fail(Name, Arity, CompileInfo, Line)
%% Purpose: creates the clause for a proxy functions, that throws an exception of "this-argument type mistmatch" (last clause)
%% Args:
%%   Name: name of method
%%   Arity: arity of the method
%%   CompileInfo: general info for transforming the compiled class (including its definition)
%%   Line: line number to set in abstract syntax tree nodes
%% Returns:
%%   * In case of using cctrans:
%%   A one-element list, that contains the abstract syntax tree representation of the 
%%   error-handling clause of the proxy function.
%%   * In case of using cctrans0, this list is empty!
implement_method_proxy_type_fail(
        Name,
        Arity,
        #compile_info{class_definition = #class{name = ThisClass}, cctrans = cctrans}, Line) ->
    Args = [ThisArg = {var, Line, 'This'}
           |lists:map(fun (_) -> {var, Line, '_'} end, lists:seq(2, Arity))],
    [{clause, Line
        ,Args
        ,[]
        ,[ast:call(throw, 
                [{tuple, Line,
                    [{atom, Line, ect_runtime_error}
                    ,{string, Line, lists:flatten(io_lib:format("this-argument type mismatch at ~p:~p/~p", [ThisClass, Name, Arity]))}
                    ,ThisArg
                    ]
                }], Line)
         ]
    }];
implement_method_proxy_type_fail(_, _, #compile_info{cctrans = cctrans0}, _) ->
    [].

%% Function: implement_method_proxy_version_check(Name, Arity, Args, CompileInfo, Line)
%% Purpose: creates the clause for a proxy functions, that invokes upgrade if the object in the 
%%   this-argument is outdated or future.
%% Args:
%%   Name: name of method
%%   Arity: arity of the method
%%   Args: the arguments of the method (a variable list)
%%   CompileInfo: general info for transforming the compiled class (including its definition)
%%   Line: line number to set in abstract syntax tree nodes
%% Returns:
%%   * In case of using cctrans and upgrade mode1:
%%   A one-element list, that contains the abstract syntax tree representation of the 
%%   upgrading clause of the proxy function.
%%   * Otherwise the list is empty!
implement_method_proxy_version_check(
    Name,
    _Arity,
    Args = [ThisArg|Args1],
    #compile_info{
        cctrans = cctrans,
        upgrade_mode = mode1,
        class_definition = Definition
    },
    Line) ->
    
    Upgraded1 = {var, Line, 'Upgraded'},
    New1 = {var, Line, 'New1'},

    [{clause, Line
        ,Args
        ,[[ast:class_typecheck_guard(Definition, ThisArg, Line),
           {op, Line, 'not',
                classes:check_global_version(Definition, ThisArg, Line)
           }
         ]]
        ,
            [{match, Line, New1, {call, Line, {remote, Line, ast:element(1, ThisArg, Line), {atom, Line, new}}, []}}
            ,{match, Line, Upgraded1, ast:mcall(New1, upgrade, [ThisArg], Line)}
            ,ast:call(Name, [Upgraded1|Args1], Line)
            ]
    }];
implement_method_proxy_version_check(_, _, _, #compile_info{cctrans = cctrans0}, _) ->
    [];
implement_method_proxy_version_check(_, _, _, #compile_info{upgrade_mode = mode2}, _) ->
    [].

%% Function: implement_method_stubs(VMT, CompileInfo, Line)
%% Purpose: creates all the method-proxy functions
%% Args:
%%   VMT: method table, it has the form of [{Name, Arity, Class, ClassImplModule}]
%%   CompileInfo: general info for transforming the compiled class (including its definition)
%%   Line: line number to set in abstract syntax tree nodes
%% Returns:
%%   List of abstract syntax tree representations of method-proxy functions.
implement_method_stubs([], _, _) ->
    [];
implement_method_stubs([{Name, Arity, _Class, Module}|VMT], ClassInfo, Line) when Arity > 0 ->
    Args = ast:var_list(Arity, Line),
    [
     {function, Line, Name, Arity, 
        case {Name, Arity} of
        {upgrade, 2} -> [];
        _ -> implement_method_proxy_version_check(Name, Arity, Args, ClassInfo, Line)
        end
        ++
        implement_method_proxy_call(Name, Arity, Args, ClassInfo, Module, Line)
        ++
        implement_method_proxy_type_fail(Name, Arity, ClassInfo, Line)
     }
     |
     implement_method_stubs(VMT, ClassInfo, Line)
    ];
implement_method_stubs([{Name, _, _, _}|VMT], ClassInfo, Line) ->
     [{error, {Line, erl_parse, io_lib:format("[ect] Method ~p has zero arity.", [Name])}}
     |implement_method_stubs(VMT, ClassInfo, Line)].

%% Function: implement_function_stubs(Functions, CompileInfo, Line)
%% Purpose: creates all the function-proxy functions
%% Args:
%%   Functions: list of exported functions, in the form of [{Name, Arity}]
%%   ImplModule: implementation module of the currently transformed class
%%   Line: line number to set in abstract syntax tree nodes
%% Returns:
%%   List of abstract syntax tree representations of function-proxy functions.
implement_function_stubs([], _, _) ->
    [];
implement_function_stubs([{Name, Arity}|Functions], ImplModule, Line) ->
    Args = ast:var_list(Arity, Line),
    [
     {function, Line, Name, Arity, 
        [{clause, Line, 
            Args,
            [],
            [ast:rcall(ImplModule, Name, ast:var_list(Arity, Line), Line)]
        }]
     }
     |
     implement_function_stubs(Functions, ImplModule, Line)
    ].

%% Function: gen_new_locals(Locals, GlobalVersion, UpgradeMode, Line)
%% Purpose: create local function to create each subtuple of a new object
%% Args:
%%   Locals: local definitions of each superclass of the class
%%   GlobalVersion: global version number of the class
%%   Line: line number to set in abstract syntax tree nodes
%% Returns:
%%   List of abstract syntax tree representations of the created functions.
gen_new_locals([], _, _, _) ->
    [];
gen_new_locals([#class_local{name = Name, version = LocalVersion, fields = LocalFields}|Locals], GlobalVersion1, UpgradeMode, Line) ->
    GlobalVersion2 = GlobalVersion1 + LocalVersion - 1,
    VersionMarker =
        case UpgradeMode of
        mode1 -> {integer, Line, GlobalVersion2};
        mode2 -> {atom , Line, list_to_atom(atom_to_list(Name)++"_proxy"++integer_to_list(GlobalVersion2))}
        end,
    [
        {tuple, Line,
            [{atom, Line, Name}
            ,{integer, Line, LocalVersion}
            ,VersionMarker
            |lists:map(fun(#class_field{value = Value}) -> Value end, LocalFields)
            ]
        }
    |gen_new_locals(Locals, GlobalVersion2, UpgradeMode, Line)
    ].

%% Function: gen_new(CompileInfo, Line)
%% Purpose: create function (new/0) that creates a new object instance of the currently transformed class.
%% Args:
%%   CompileInfo: general info for transforming the compiled class (including its definition)
%%   Line: line number to set in abstract syntax tree nodes
%% Returns:
%%   Abstract syntax tree representations of the created function:
%%    new/0 that returns a new object instance.
gen_new(#compile_info{proxy_module = ProxyModule, class_definition = #class{src = LocalDefs}, upgrade_mode = UpgradeMode, cctrans = cctrans}, Line) ->
    {function, Line, new, 0,
      [{clause, Line,
        [],
        [],
        [
         {tuple, Line, 
           [{atom, Line, ProxyModule}
           |gen_new_locals(LocalDefs, 1, UpgradeMode, Line)
           ]
         }
        ]
       }]
     }.

%% Function: create_proxy_big(CompileInfo)
%% Purpose: creates the abstract syntax tree representation of a proxy module
%% Args:
%%   CompileInfo: general info for transforming the compiled class (including its definition)
%% Returns:
%%   The resulting abstract syntax tree represents a module that contains function proxies and a new/0 and rnew/0 function.
%%   Depending on upgrade mode, there can also be method proxies in the module.
create_proxy_big(ClassInfo = 
                           #compile_info{class_definition = Definition,
                                      cctrans = Mode,
                                      upgrade_mode = UpgradeMode}) ->

    #class{
        name = ThisClass,
        vmt = VMT,
        functions = NonVMTExports,
        impl_module = ImplModule,
        loaded_subclasses = LoadedSubClasses
    } = Definition,

    VMExports = 
        case UpgradeMode of
        mode1 -> create_exports(VMT);
        mode2 -> []
        end,

    Line = 1,
    [{attribute, Line, file, {atom_to_list(ThisClass)++".erl", 1}}
    ,{attribute, Line, module, ThisClass}
    ,{attribute, Line, export, NonVMTExports}
    ,{attribute, Line, export, VMExports}
    ,{attribute, Line, impl_module, ImplModule}
    ,{attribute, Line, loaded_subclasses, LoadedSubClasses}
    ]
    ++
    case Mode of
    cctrans -> 
        [{attribute, Line, export, [{new, 0}, {rnew, 0}, {rserver, 1}]}
        ,gen_new(ClassInfo, Line)
        ,remotetrans:gen_rnew(ClassInfo, Line)
        ,remotetrans:gen_rserver(ClassInfo, Line)
        ];
    cctrans0 ->
        []
    end
    ++
    case UpgradeMode of
    mode1 -> implement_method_stubs(VMT, ClassInfo, Line);
    mode2 -> []
    end
    ++
    implement_function_stubs(NonVMTExports, ImplModule, Line)
    ++[{eof, Line}].

%% Function: create_proxy_small(CompileInfo)
%% Purpose: creates the abstract syntax tree representation of a proxy module (more precisely a "method proxy" module)
%% Args:
%%   CompileInfo: general info for transforming the compiled class (including its definition)
%% Returns:
%%   The resulting abstract syntax tree represents a module that contains only method proxies.
create_proxy_small(ClassInfo = #compile_info{proxy_module = ProxyModule, class_definition = #class{vmt = VMT}}) ->
    Line = 1,
    VMExports = create_exports(VMT),
    [{attribute, Line, file, {atom_to_list(ProxyModule)++".erl", 1}}
    ,{attribute, Line, module, ProxyModule}
    ,{attribute, Line, export, VMExports}
    |implement_method_stubs(VMT, ClassInfo, Line)
    ].

%% Function: create_proxy(CompileInfo)
%% Purpose: Creates the abstract syntax tree(s) of proxy module(s) of a class. The number of trees depend on the upgrade mode.
%% Args:
%%   CompileInfo: general info for transforming the compiled class (including its definition)
%% Returns:
%%   In upgrade mode1, a one-element list containing the AST of the proxy module.
%%   In upgrade mode2, a two-element list containing the ASTs of the proxy module and the method proxy module.
create_proxy(ClassInfo1 = #compile_info{class_definition = #class{name = ThisClass}, upgrade_mode = mode1}) ->
    ClassInfo2 = ClassInfo1#compile_info{proxy_module = ThisClass},
    [{create_proxy_big(ClassInfo2), ThisClass}];
create_proxy(
    ClassInfo1 = #compile_info{class_definition = #class{name = ThisClass, global_version = GlobalVersion}, upgrade_mode = mode2}) ->
    ProxyModule = list_to_atom(atom_to_list(ThisClass)++"_proxy"++integer_to_list(GlobalVersion)),
    ClassInfo2 = ClassInfo1#compile_info{proxy_module = ProxyModule},

    %io:format("mode2 proxy_module=~p~n", [ProxyModule]),
    [{create_proxy_big(ClassInfo2), ThisClass}
    ,{create_proxy_small(ClassInfo2), ProxyModule}
    ].